package org.codefinger.json.util.type;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

import org.codefinger.json.util.IdentityCache;
import org.codefinger.json.util.IdentityCache.ValueBuilder;
import org.codefinger.json.util.Lang;

public class Generic {

	public static final Type[]							NULL_GENERICS	= new Type[0];

	private static final IdentityCache<Type, Generic>	GENERIC_MAP		= new IdentityCache<Type, Generic>(new GenericBuilder());

	private Class<?>									clazz;

	private Type[]										generics;

	private boolean										isArray;

	private Type										componentType;

	private Generic(Class<?> clazz, Type[] generics, boolean isArray, Type componentType) {
		super();
		this.generics = generics;
		this.clazz = clazz;
		this.isArray = isArray;
		this.componentType = componentType;
	}

	public Class<?> getRealClass() {
		return clazz;
	}

	public Type[] getGenerics() {
		return generics;
	}

	public boolean isArray() {
		return isArray;
	}

	public Type getComponentType() {
		return componentType;
	}

	public static Generic getGeneric(Type genericType) {
		return GENERIC_MAP.get(genericType);
	}

	private static class GenericBuilder implements ValueBuilder<Type, Generic> {

		@SuppressWarnings("rawtypes")
		@Override
		public Generic build(Type genericType) {
			if (genericType instanceof Class) {

				Class<?> clazz = (Class) genericType;
				boolean isArray = clazz.isArray();
				return new Generic(isArray ? clazz.getComponentType() : clazz, NULL_GENERICS, isArray, isArray ? clazz.getComponentType() : null);

			} else if (genericType instanceof ParameterizedType) {

				ParameterizedType parameterizedType = (ParameterizedType) genericType;
				Type type = parameterizedType.getRawType();
				return new Generic(type == null ? (Class) parameterizedType.getOwnerType() : ((Class) type), parameterizedType.getActualTypeArguments(), false, null);

			} else if (genericType instanceof WildcardType) {

				WildcardType wildcardType = (WildcardType) genericType;
				Type[] types = wildcardType.getUpperBounds();
				if (types.length > 0) {
					return getGeneric(types[0]);
				}
				return new Generic(Object.class, NULL_GENERICS, false, null);

			} else if (genericType instanceof TypeVariable) {

				TypeVariable typeVariable = (TypeVariable) genericType;
				Type[] types = typeVariable.getBounds();
				if (types.length > 0) {
					return getGeneric(types[0]);
				}
				return new Generic(Object.class, NULL_GENERICS, false, null);

			} else if (genericType instanceof GenericArrayType) {

				GenericArrayType genericArrayType = (GenericArrayType) genericType;
				Type type = genericArrayType.getGenericComponentType();
				Type componentType = type;
				StringBuilder builder = new StringBuilder();
				while (type instanceof GenericArrayType) {
					builder.append("[");
					type = ((GenericArrayType) type).getGenericComponentType();
				}
				Generic generic = getGeneric(type);
				if (builder.length() == 0) {
					return new Generic(generic.getRealClass(), generic.getGenerics(), true, componentType);
				} else {
					try {
						return new Generic(Class.forName(builder.append("L").append(generic.getRealClass().getName()).append(";").toString()), generic.getGenerics(), true, componentType);
					} catch (ClassNotFoundException e) {
						throw Lang.makeThrow("It is impossible!");
					}
				}

			} else {
				throw Lang.makeThrow("It is impossible!");
			}
		}

	}

}
